﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

using RpgLibrary.WorldClasses;
using RpgLibrary.CharacterClasses;

using XRpgLibrary.CharacterClasses;
using XRpgLibrary.ExtensionMethods;
using XRpgLibrary.ScriptClasses;

namespace XRpgLibrary.TileEngine
{
    public class MapLayer : INotifyPropertyChanged
    {
        #region Field Region

        Tile[,] layer;
        PassTile[,] passLayer;
        public CharacterManager CharacterManager;
        public bool EntitiesActive = true;
        public bool TilesActive = true;
        public int TilesetIndex = -1;
        private Camera camera = null;

        public event PropertyChangedEventHandler PropertyChanged;
        #endregion

        #region Property Region


        [Browsable(true)]
        public string Name { get; set; }
        
        [Browsable(true)]
        public bool Visible { get; set; }

        [Browsable(false)]
        public int Width
        {
            get { return layer.GetLength(1); }
        }

        [Browsable(false)]
        public int Height
        {
            get { return layer.GetLength(0); }
        }

        #endregion

        #region Constructor Region

        //public MapLayer(Tile[,] layer, PassTile[,] passLayer, CharacterManager entityManager)
        //{
        //    this.layer = layer;
        //    this.passLayer = passLayer;
        //    this.CharacterManager = entityManager;
        //    this.Visible = true;
        //}

        public MapLayer(MapLayerData data, Camera camera)
            : this (data.Width, data.Height)
        {
            Visible = data.Visible;
            Name = data.LayerName;
            TilesetIndex = data.TileSetIndex;
            this.camera = camera;

            for (int y = 0; y < data.Height; y++)
            {
                for (int x = 0; x < data.Width; x++)
                {
                    passLayer[y, x] = data.PassLayer[y * data.Height + x];
                }
            }
        }

        public MapLayer(int width, int height)
        {
            layer = new Tile[height, width];
            passLayer = new PassTile[height, width];
            CharacterManager = new CharacterManager(passLayer);
            Visible = true;
            TilesetIndex = -1;
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    layer[y, x] = new Tile(0);
                    passLayer[y, x] = new PassTile(0);
                }
            }
        }

        #endregion

        public void Update(GameTime gameTime)
        {
            if (EntitiesActive)
                CharacterManager.Update(gameTime);

            //Point min;
            //Point max;
            //GetVisibleTileArea(camera, out min, out max);

            //for (int y = min.Y; y < max.Y; y++)
            //    for (int x = min.X; x < max.X; x++)
            //        GetTile(x, y).Update(gameTime);
        }


        public void DrawPass(GameTime gameTime, SpriteBatch spriteBatch, Camera camera, Texture2D passTexture)
        {
            Point cameraPoint = Engine.VectorToCell(camera.Position * (1 / camera.Zoom));
            Point viewPoint = Engine.VectorToCell(
                new Vector2(
                    (camera.Position.X + camera.ViewportRectangle.Width) * (1 / camera.Zoom),
                    (camera.Position.Y + camera.ViewportRectangle.Height) * (1 / camera.Zoom)));

            Point min = new Point();
            Point max = new Point();

            min.X = Math.Max(0, cameraPoint.X - 1);
            min.Y = Math.Max(0, cameraPoint.Y - 1);
            max.X = Math.Min(viewPoint.X + 1, Width);
            max.Y = Math.Min(viewPoint.Y + 1, Height);

            Rectangle destination = new Rectangle(0, 0, Engine.TileWidth, Engine.TileHeight);
            PassTile tile;

            for (int y = min.Y; y < max.Y; y++)
            {
                destination.Y = y * Engine.TileHeight;

                for (int x = min.X; x < max.X; x++)
                {
                    destination.X = x * Engine.TileWidth;

                    tile = GetPassTile(x, y);
                    if (tile.PassRects != null)
                        for (int i = 0, cnt = tile.PassRects.Count; i < cnt; i++)
                        {
                            Rectangle rect = tile.PassRects[i];
                            rect.X += destination.X;
                            rect.Y += destination.Y;

                            spriteBatch.Draw(
                                passTexture,
                                rect,
                                Color.White);
                        }
                }
            }
        }

        public void DrawPassRelative(GameTime gameTime, SpriteBatch spriteBatch, Camera camera, Texture2D passTexture)
        {
            Point cameraPoint = Engine.VectorToCell(camera.Position * (1 / camera.Zoom));
            Point viewPoint = Engine.VectorToCell(
                new Vector2(
                    (camera.Position.X + camera.ViewportRectangle.Width) * (1 / camera.Zoom),
                    (camera.Position.Y + camera.ViewportRectangle.Height) * (1 / camera.Zoom)));

            Point min = new Point();
            Point max = new Point();

            min.X = Math.Max(0, cameraPoint.X - 1);
            min.Y = Math.Max(0, cameraPoint.Y - 1);
            max.X = Math.Min(viewPoint.X + 1, Width);
            max.Y = Math.Min(viewPoint.Y + 1, Height);

            for (int y = min.Y; y < max.Y; y++)
                for (int x = min.X; x < max.X; x++)
                    if (passLayer[y,x].PassRects != null)
                        for (int i = 0, cnt = passLayer[y, x].PassRects.Count; i < cnt; i++)
                            spriteBatch.Draw(
                                passTexture,
                                passLayer[y, x].PassRects[i],
                                Color.White);
        }

        public void DrawMini(GameTime gameTime, SpriteBatch spriteBatch, Camera camera, List<Tileset> tilesets)
        {
            if (TilesActive && Visible)
                DrawMiniTiles(spriteBatch, camera, tilesets);
        }

        public void Draw(GameTime gameTime, SpriteBatch spriteBatch, Camera camera, List<Tileset> tilesets)
        {
            if (Visible)
            {
                if (TilesActive)
                    DrawTilesCamera(spriteBatch, camera, tilesets);

                if (EntitiesActive)
                    CharacterManager.Draw(gameTime, camera);
            }
        }

        public void DrawExport(Rectangle camera, SpriteBatch spriteBatch, List<Tileset> tilesets)
        {
            if (Visible)
            {
                if (TilesActive)
                    DrawTilesExport(camera, spriteBatch, tilesets);

                if (EntitiesActive)
                    CharacterManager.Draw(spriteBatch);

            }
        }

        private void DrawTilesCamera(SpriteBatch spriteBatch, Camera camera, List<Tileset> tilesets)
        {
            Point min;
            Point max;
            GetVisibleTileArea(camera, out min, out max);

            DrawTiles(spriteBatch, tilesets, min, max);
        }

        private void GetVisibleTileArea(Camera camera, out Point min, out Point max)
        {
            if (camera == null)
                camera = new Camera(new Rectangle(0,0,Width * Engine.TileWidth, Height * Engine.TileHeight), new Vector2(0,0));

            Point cameraPoint = Engine.VectorToCell(camera.Position * (1 / camera.Zoom));
            Point viewPoint = Engine.VectorToCell(
                new Vector2(
                    (camera.Position.X + camera.ViewportRectangle.Width) * (1 / camera.Zoom),
                    (camera.Position.Y + camera.ViewportRectangle.Height) * (1 / camera.Zoom)));

            min = new Point();
            max = new Point();

            min.X = Math.Max(0, cameraPoint.X - 1);
            min.Y = Math.Max(0, cameraPoint.Y - 1);
            max.X = Math.Min(viewPoint.X + 1, Width);
            max.Y = Math.Min(viewPoint.Y + 1, Height);
        }

        private void DrawTilesExport(Rectangle camera, SpriteBatch spriteBatch, List<Tileset> tilesets)
        {
            Point min = new Point(camera.Left, camera.Top);
            Point max = new Point(camera.Bottom, camera.Right);

            Rectangle destination = new Rectangle(0, 0, Engine.TileWidth, Engine.TileHeight);

            for (int y = min.Y; y < max.Y; y++)
            {
                destination.Y = (y - min.Y) * Engine.TileHeight;

                for (int x = min.X; x < max.X; x++)
                {
                    if (layer[y, x].TileIndex == -1 || TilesetIndex == -1)
                        continue;

                    destination.X = (x - min.X) * Engine.TileWidth;

                    spriteBatch.Draw(
                        tilesets[TilesetIndex].Texture,
                        destination,
                        tilesets[TilesetIndex].SourceRectangles[layer[y, x].TileIndex],
                        Color.White);
                }
            }
        }

        private void DrawTiles(SpriteBatch spriteBatch, List<Tileset> tilesets, Point min, Point max)
        {
            Rectangle destination = new Rectangle(0, 0, Engine.TileWidth, Engine.TileHeight);
            Tile tile;

            for (int y = min.Y; y < max.Y; y++)
            {
                destination.Y = y * Engine.TileHeight;

                for (int x = min.X; x < max.X; x++)
                {
                    tile = GetTile(x, y);

                    if (tile.TileIndex == -1 || TilesetIndex == -1)
                        continue;

                    destination.X = x * Engine.TileWidth;

                    spriteBatch.Draw(
                        tilesets[TilesetIndex].Texture,
                        destination,
                        tilesets[TilesetIndex].SourceRectangles[tile.TileIndex],
                        Color.White);
                }
            }
        }

        private void DrawMiniTiles(SpriteBatch spriteBatch, Camera camera, List<Tileset> tilesets)
        {
            Tile tile;

            for (int y = 0; y < Height; y++)
            {
                for (int x = 0; x < Width; x++)
                {
                    tile = GetTile(x, y);

                    if (tile.TileIndex == -1 ||TilesetIndex == -1)
                        continue;

                    spriteBatch.Draw(
                        tilesets[TilesetIndex].GetAverage(tile.TileIndex, spriteBatch.GraphicsDevice),
                        new Rectangle(x+1, y+1, 1, 1),
                        Color.White);
                }
            }
        }

        #region Method Region
        public Tile GetTile(int x, int y)
        {
            return layer[y, x];
        }

        public PassTile GetPassTile(int x, int y)
        {
            return passLayer[y, x];
        }

        public void SetTile(int x, int y, Tile tile)
        {
            layer[y, x] = tile;
        }

        public void SetTileRow(Point startPoint, int y, int x1, int x2, Tile[,] tileMatrix)
        {
            for(int x = x1; x < x2+1;x++)
                SetTile(x,y, tileMatrix[Math.Abs(startPoint.Y - y) % tileMatrix.GetLength(0),
                                        Math.Abs(startPoint.X - x) % tileMatrix.GetLength(1)]);
        }

        public void SetTile(int x, int y, int tileIndex)
        {
            SetTile(x,y, new Tile(tileIndex));
        }

        public void SetPassTile(int x, int y, PassTile tile)
        {
            passLayer[y, x] = tile;
        }

        public void SetPassTile(int x, int y, int tileIndex, List<Rectangle> passRects)
        {
            passLayer[y, x] = new PassTile(tileIndex, passRects);
        }

        public void FloodFill(int x, int y, Tile tile)
        {
            FloodFill(x, y, new Tile[1, 1] { { tile } });
        }

        public void FloodFill(int x, int y, Tile[,] replacementMatrix)
        {
            if (!IsLegal(x, y))
                return;

            Tile targetTile = layer[y, x];

            Queue<Point> tileCoordQueue = new Queue<Point>();
            tileCoordQueue.Enqueue(new Point(x, y));

            while (tileCoordQueue.Count > 0)
            {
                Point currentTileCoords = tileCoordQueue.Dequeue();
                int w = currentTileCoords.X;
                int e = currentTileCoords.X;

                while (IsLegal(w-1, currentTileCoords.Y) && layer[currentTileCoords.Y, w-1] == targetTile)
                    w--;
                
                while (IsLegal(e+1, currentTileCoords.Y) && layer[currentTileCoords.Y, e+1] == targetTile)
                    e++;

                SetTileRow(new Point(x,y), currentTileCoords.Y, w, e, replacementMatrix);

                for(int col = w, colMax = e+1; col < colMax; col++)
                {
                    if (IsLegal(col, currentTileCoords.Y+1) && layer[currentTileCoords.Y+1, col] == targetTile)
                        tileCoordQueue.Enqueue(new Point(col, currentTileCoords.Y + 1));
                    
                    if (IsLegal(col, currentTileCoords.Y-1) && layer[currentTileCoords.Y-1, col] == targetTile)
                        tileCoordQueue.Enqueue(new Point(col, currentTileCoords.Y - 1));
                }
            }
        }

        #endregion

        public PassTile GetTilePassability(int x, int y)
        {
            return passLayer[y,x];
        }

        public bool HasPassability(int x, int y)
        {
            return passLayer[y, x].PassRects != null && passLayer[y, x].PassRects.Count > 0;
        }

        public bool ClearPassWall(int x, int y, int wall)
        {
            //wall is 0 1 2 3 = N S E W
            if(passLayer[y, x].PassRects != null)
                for(int i = 0, cnt = passLayer[y, x].PassRects.Count; i < cnt; i++)
                    switch (wall)
                    {
                        case 0: //N
                            if (passLayer[y, x].PassRects[i].Y == 0
                                && passLayer[y, x].PassRects[i].Width > Engine.TileHeight / 4)
                            {
                                passLayer[y, x].PassRects.RemoveAt(i);
                                return true;
                            }
                            break;

                        case 1: //S
                            if (passLayer[y, x].PassRects[i].Y + passLayer[y, x].PassRects[i].Height == Engine.TileHeight
                                && passLayer[y, x].PassRects[i].Width > Engine.TileHeight / 4)
                            {
                                passLayer[y, x].PassRects.RemoveAt(i);
                                return true;
                            }
                            break;

                        case 2: // E
                            if (passLayer[y, x].PassRects[i].X + passLayer[y, x].PassRects[i].Width == Engine.TileWidth
                                && passLayer[y, x].PassRects[i].Height > Engine.TileWidth / 4)
                            {
                                passLayer[y, x].PassRects.RemoveAt(i);
                                return true;
                            }
                            break;
                        case 3: // W
                            if (passLayer[y, x].PassRects[i].X == 0
                                && passLayer[y, x].PassRects[i].Height > Engine.TileWidth / 4)
                            {
                                passLayer[y, x].PassRects.RemoveAt(i);
                                return true;
                            }
                            break;
                    }

            return false;
        }


        public void ClearPassTile(int x, int y)
        {
            if (passLayer[y, x].PassRects != null)
                passLayer[y, x].PassRects.Clear();
        }

        public void SetPassWall(int x, int y, int wall)
        {
            //wall is 0 1 2 3 = N S E W
            if (passLayer[y, x].PassRects == null)
                passLayer[y, x].PassRects = new List<Rectangle>();

            Rectangle rect = new Rectangle();

            switch (wall)
            {
                case 0: //N
                    rect.X = 0;
                    rect.Y = 0;
                    rect.Width = Engine.TileWidth;
                    rect.Height = (int)(Engine.TileHeight * .25);
                    break;

                case 1: //S
                    rect.X = 0;
                    rect.Y = (int)(Engine.TileHeight * .75);
                    rect.Width = Engine.TileWidth;
                    rect.Height = (int)(Engine.TileHeight * .25);
                    break;

                case 2: // E
                    rect.X = (int)(Engine.TileWidth * .75);
                    rect.Y = 0;
                    rect.Width = (int)(Engine.TileHeight * .25);
                    rect.Height = Engine.TileHeight;
                    break;
                case 3: // W
                    rect.X = 0;
                    rect.Y = 0;
                    rect.Width = (int)(Engine.TileHeight * .25);
                    rect.Height = Engine.TileHeight;
                    break;
            }

            passLayer[y, x].PassRects.Add(rect);
        }

        public void SetPassSimple(int x, int y)
        {
            if (passLayer[y, x].PassRects == null)
                passLayer[y, x].PassRects = new List<Rectangle>();

            passLayer[y, x].PassRects.Add(new Rectangle(0, 0, Engine.TileWidth, Engine.TileHeight));
        }

        private bool IsLegal(int x, int y)
        {
            return 
                y > -1 
             && x > -1 
             && y < layer.GetLength(0) 
             && x < layer.GetLength(1);
        }

        public void RemoveEntitiesAtTile(Point tile)
        {
            Vector4 tileRect = new Vector4(
                (tile.X * Engine.TileWidth),
                (tile.Y * Engine.TileHeight),
                (Engine.TileWidth),
                (Engine.TileHeight));

            List<Character> delete = new List<Character>();

            foreach (Character character in CharacterManager.Characters)
            {
                if (character.CollisionBox.Intersects(tileRect))
                    delete.Add(character);
            }

            foreach (Character character in delete)
                CharacterManager.Characters.Remove(character);
        }

        public static MapLayer FromLayerData(
            MapLayerData data, 
            SpriteBatch spriteBatch, 
            ContentManager contentManager,
            ScriptContext scriptContext, 
            Camera camera)
        {
            MapLayer layer = new MapLayer(data, camera);

            for (int y = 0; y < data.Height; y++)
                for (int x = 0; x < data.Width; x++)
                {
                    layer.SetTile(
                        x,
                        y,
                        data.GetTile(x, y).TileIndex);

                    layer.SetPassTile(
                        x,
                        y,
                        data.GetPassTile(x, y).TileIndex,
                        data.GetPassTile(x, y).PassRects);
                }

            layer.CharacterManager = CharacterManager.FromCharacterData(
                data.Characters, 
                spriteBatch, 
                contentManager, 
                scriptContext);
            layer.CharacterManager.PassMap = layer.passLayer;

            return layer;

        }

        private void NotifyPropertyChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

        public MapLayerData GetDataObject()
        {
            MapLayerData data = new MapLayerData(
                this.Name,
                this.Width,
                this.Height,
                this.Visible, 
                -1, 
                this.TilesetIndex,
                GenerateCharacterData());

            for (int y = 0; y < this.Height; y++)
                for (int x = 0; x < this.Width; x++)
                {
                    XRpgLibrary.TileEngine.Tile tile = this.GetTile(x, y);
                    data.SetTile(
                           x,
                           y,
                           tile.TileIndex);

                    data.SetPassTile(
                           x,
                           y,
                           tile.TileIndex,
                           this.GetPassTile(x, y).PassRects);
                }

            return data;
        }

        private List<CharacterData> GenerateCharacterData()
        {
            List<CharacterData> data = new List<CharacterData>();

            foreach (Character character in this.CharacterManager.Characters)
                data.Add(character.GetDataObject());
            
            return data;
        }

        public MapLayer Clone(string layerName, SpriteBatch spriteBatch, ContentManager contentManager)
        {
            MapLayer newLayer = MapLayer.FromLayerData(
                this.GetDataObject(),
                spriteBatch,
                contentManager, 
                null,
                camera);

            if (string.IsNullOrEmpty(layerName))
                newLayer.Name += " Copy";
            else
                newLayer.Name = layerName;

            return newLayer;
        }
    }
}
